import { CheckStream, EffDiff, Effect, GAHandler, Stream, eff, handle, mkHandler, run } from './main'

export const write = eff('write')<[string], void>

export const writer = <R>(r = '') => mkHandler<
  Effect<'write', [string], void>, never,
  R, [string, R]
>({
  *return(val) {
    return [r, val]
  },
  *write(cont, val) {
    r += val
    return cont(undefined, true)
  },
})

export const pureWriter = <R>() => mkHandler<
  Effect<'write', [string], void>, never,
  R, [string, R]
>({
  *return(val) {
    return ['', val]
  },
  *write(cont, val) {
    const [r, ret] = yield* cont(undefined)
    return [val + r, ret]
  },
})

type AwaitEff<T, A = Awaited<T>> = Effect<'await', [T], A>
type TrueAwaitEffOf<E> = E extends infer E ? E extends AwaitEff<infer T, any> ? AwaitEff<T> : never : never

export const awaitEff = ((e = eff('await')) =>
  <T>(promise: T) => e<[T], Awaited<T>>(promise))()

export const promise = <R, E extends AwaitEff<any>>(
  stream: CheckStream<E, TrueAwaitEffOf<E>, R>
) => handle<
  AwaitEff<any>, AwaitEff<any>, never, R, Promise<Awaited<R>>
>(stream, {
  *return(val) {
    return Promise.resolve(val)
  },
  *await(cont, val) {
    return Promise.resolve(val).then(val => run(cont(val)))
  },
})

export const reader = <T>(value: T)
  : GAHandler<Effect<string, unknown[], T>, never> =>
  function* (cont) { return cont(value, true) }

type StateEff<T> = Effect<'get', [], T> | Effect<'set', [T], void>

export const pureState = <
  E1 extends Effect,
  R,
  T = E1 extends StateEff<infer T> ? T : unknown
>(stream: CheckStream<E1, StateEff<T>, R>) => handle<
  StateEff<T>, E1, never,
  R, (state: T) => Stream<EffDiff<E1, StateEff<T>>, [T, R]>
>(stream, {
  *return(val) {
    return function* (state) { return [state, val] }
  },
  *get(cont) {
    return function* (state) {
      return yield* (yield* cont(state))(state)
    }
  },
  *set(cont, val) {
    return function* (_) {
      return yield* (yield* cont(undefined))(val)
    }
  },
})

export const state = <T, R>(ref: T) => mkHandler<
  StateEff<T>, never,
  R, [T, R]
>({
  *return(val) {
    return [ref, val]
  },
  *get(cont) {
    return yield* cont(ref)
  },
  *set(cont, val) {
    ref = val
    return yield* cont(undefined)
  },
})

export const exn = mkHandler<
  Effect<'raise', [string], never>, never
>({
  *raise(_, err) {
    throw err
  }
})
